package com.google.code.jtasks.bugzilla.proxy;

import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Pattern;

/**
 * This class contains a number of static utility methods used by other classes in this package.
 */
public final class ProcessingUtils
{
	/**
	 * A simple {@link Pattern} that will verify whether a given {@link String} represents a valid
	 * {@link Integer} or not.
	 */
	public static final Pattern PATTERN_INTEGER = Pattern.compile("-?\\d+");

	/**
	 * Extracts the specified field value from the specified {@link Map} as a {@link Boolean}.
	 * 
	 * @param fieldMap
	 *            the {@link Map} of field values
	 * @param fieldName
	 *            the name of the field to extract
	 * @param nullAllowed
	 *            <code>true</code> if this method should return <code>null</code> if the specified field
	 *            cannot be found, <code>false</code> if an {@link IllegalArgumentException} should be thrown
	 *            instead
	 * @return the specified field value from the specified {@link Map} as a {@link Boolean}
	 */
	public static Boolean extractBooleanField(Map<?, ?> fieldMap, String fieldName, boolean nullAllowed)
	{
		if (!nullAllowed && !fieldMap.containsKey(fieldName))
			throw new IllegalArgumentException(String.format("Field '%s' is missing from map: %s", fieldName,
					fieldMap));
		Object untypedField = fieldMap.get(fieldName);
		if (untypedField != null && !(untypedField instanceof Boolean))
			throw new IllegalArgumentException("Unexpected type for field: " + untypedField
					+ ". Expected a Boolean.");
		return (Boolean) untypedField;
	}

	/**
	 * Extracts the specified field value from the specified {@link Map} as an {@link Integer}.
	 * 
	 * @param fieldMap
	 *            the {@link Map} of field values
	 * @param fieldName
	 *            the name of the field to extract
	 * @param nullAllowed
	 *            <code>true</code> if this method should return <code>null</code> if the specified field
	 *            cannot be found, <code>false</code> if an {@link IllegalArgumentException} should be thrown
	 *            instead
	 * @return the specified field value from the specified {@link Map} as an {@link Integer}
	 */
	public static Integer extractIntegerField(Map<?, ?> fieldMap, String fieldName, boolean nullAllowed)
	{
		if (!nullAllowed && !fieldMap.containsKey(fieldName))
			throw new IllegalArgumentException(String.format("Field '%s' is missing from map: %s", fieldName,
					fieldMap));
		Object untypedField = fieldMap.get(fieldName);
		if (untypedField != null && !(untypedField instanceof Integer))
			throw new IllegalArgumentException("Unexpected type for field: " + untypedField
					+ ". Expected a Integer.");
		return (Integer) untypedField;
	}

	/**
	 * Extracts the specified field value from the specified {@link Map} as a {@link Double}.
	 * 
	 * @param fieldMap
	 *            the {@link Map} of field values
	 * @param fieldName
	 *            the name of the field to extract
	 * @return the specified field value from the specified {@link Map} as a {@link Double}
	 */
	public static Double extractDoubleField(Map<?, ?> fieldMap, String fieldName)
	{
		if (!fieldMap.containsKey(fieldName))
			throw new IllegalArgumentException(String.format("Field '%s' is missing from map: %s", fieldName,
					fieldMap));
		Object untypedField = fieldMap.get(fieldName);
		if (!(untypedField instanceof Double))
			throw new IllegalArgumentException("Unexpected type for field: " + untypedField
					+ ". Expected a Double.");
		return (Double) untypedField;
	}

	/**
	 * Extracts the specified field value from the specified {@link Map} as a {@link String}.
	 * 
	 * @param fieldMap
	 *            the {@link Map} of field values
	 * @param fieldName
	 *            the name of the field to extract
	 * @param nullAllowed
	 *            <code>true</code> if this method should return <code>null</code> if the specified field
	 *            cannot be found, <code>false</code> if an {@link IllegalArgumentException} should be thrown
	 *            instead
	 * @return the specified field value from the specified {@link Map} as a {@link String}
	 */
	public static String extractStringField(Map<?, ?> fieldMap, String fieldName, boolean nullAllowed)
	{
		if (!nullAllowed && !fieldMap.containsKey(fieldName))
			throw new IllegalArgumentException(String.format("Field '%s' is missing from map: %s", fieldName,
					fieldMap));
		Object untypedField = fieldMap.get(fieldName);
		if (untypedField != null && !(untypedField instanceof String))
			throw new IllegalArgumentException("Unexpected type for field: " + untypedField
					+ ". Expected a String.");
		return (String) untypedField;
	}

	/**
	 * Extracts the specified field value from the specified {@link Map} as a {@link Date}.
	 * 
	 * @param fieldMap
	 *            the {@link Map} of field values
	 * @param fieldName
	 *            the name of the field to extract
	 * @return the specified field value from the specified {@link Map} as a {@link Date}
	 */
	public static Date extractDateField(Map<?, ?> fieldMap, String fieldName)
	{
		if (!fieldMap.containsKey(fieldName))
			throw new IllegalArgumentException(String.format("Field '%s' is missing from map: %s", fieldName,
					fieldMap));
		Object untypedField = fieldMap.get(fieldName);
		if (!(untypedField instanceof Date))
			throw new IllegalArgumentException("Unexpected type for field: " + untypedField
					+ ". Expected a Date.");
		return (Date) untypedField;
	}

	/**
	 * Extracts the specified field value from the specified {@link Map} as an {@link Object} array.
	 * 
	 * @param fieldMap
	 *            the {@link Map} of field values
	 * @param fieldName
	 *            the name of the field to extract
	 * @param nullAllowed
	 *            <code>true</code> if this method should return <code>null</code> if the specified field
	 *            cannot be found, <code>false</code> if an {@link IllegalArgumentException} should be thrown
	 *            instead
	 * @return the specified field value from the specified {@link Map} as an {@link Object} array
	 */
	public static Object[] extractObjectArrayField(Map<?, ?> fieldMap, String fieldName, boolean nullAllowed)
	{
		if (!nullAllowed && !fieldMap.containsKey(fieldName))
			throw new IllegalArgumentException(String.format("Field '%s' is missing from map: %s", fieldName,
					fieldMap));
		Object untypedField = fieldMap.get(fieldName);
		if (untypedField != null && !(untypedField instanceof Object[]))
			throw new IllegalArgumentException("Unexpected type for field: " + untypedField
					+ ". Expected an Object array.");
		return (Object[]) untypedField;
	}

	/**
	 * Extracts the specified field value from the specified {@link Map} as an {@link Integer} array.
	 * 
	 * @param fieldMap
	 *            the {@link Map} of field values
	 * @param fieldName
	 *            the name of the field to extract
	 * @return the specified field value from the specified {@link Map} as an {@link Integer} array
	 */
	public static Integer[] extractIntegerArrayField(Map<?, ?> fieldMap, String fieldName)
	{
		if (!fieldMap.containsKey(fieldName))
			throw new IllegalArgumentException(String.format("Field '%s' is missing from map: %s", fieldName,
					fieldMap));
		Object untypedField = fieldMap.get(fieldName);
		if (!(untypedField instanceof Object[]))
			throw new IllegalArgumentException("Unexpected type for field: " + untypedField
					+ ". Expected an Object array.");
		Object[] untypedArray = (Object[]) untypedField;

		Integer[] typedArray = new Integer[untypedArray.length];
		for (int i = 0; i < untypedArray.length; i++)
		{
			if (!(untypedArray[i] instanceof Integer))
				throw new IllegalArgumentException(String.format("Element %d is not an Integer: %s", i,
						untypedArray[i]));
			typedArray[i] = (Integer) untypedArray[i];
		}

		return typedArray;
	}

	/**
	 * Extracts the specified field value from the specified {@link Map} as a {@link String} array.
	 * 
	 * @param fieldMap
	 *            the {@link Map} of field values
	 * @param fieldName
	 *            the name of the field to extract
	 * @return the specified field value from the specified {@link Map} as a {@link String} array
	 */
	public static String[] extractStringArrayField(Map<?, ?> fieldMap, String fieldName)
	{
		if (!fieldMap.containsKey(fieldName))
			throw new IllegalArgumentException(String.format("Field '%s' is missing from map: %s", fieldName,
					fieldMap));
		Object untypedField = fieldMap.get(fieldName);
		if (!(untypedField instanceof Object[]))
			throw new IllegalArgumentException("Unexpected type for field: " + untypedField
					+ ". Expected an Object array.");
		Object[] untypedArray = (Object[]) untypedField;

		String[] typedArray = new String[untypedArray.length];
		for (int i = 0; i < untypedArray.length; i++)
		{
			if (!(untypedArray[i] instanceof String))
				throw new IllegalArgumentException(String.format("Element %d is not a String: %s", i,
						untypedArray[i]));
			typedArray[i] = (String) untypedArray[i];
		}

		return typedArray;
	}

	/**
	 * Extracts the specified field value from the specified {@link Map} as a {@link Map}.
	 * 
	 * @param fieldMap
	 *            the {@link Map} of field values
	 * @param fieldName
	 *            the name of the field to extract
	 * @return the specified field value from the specified {@link Map} as a {@link Map}
	 */
	public static Map<?, ?> extractMapField(Map<?, ?> fieldMap, String fieldName)
	{
		if (!fieldMap.containsKey(fieldName))
			throw new IllegalArgumentException(String.format("Field '%s' is missing from map: %s", fieldName,
					fieldMap));
		Object untypedField = fieldMap.get(fieldName);
		if (!(untypedField instanceof Map<?, ?>))
			throw new IllegalArgumentException("Unexpected type for field: " + untypedField
					+ ". Expected a Map.");
		return (Map<?, ?>) untypedField;
	}

	/**
	 * Extracts the {@link String} key from the specified {@link Entry} as an {@link Integer}.
	 * 
	 * @param untypedMapEntry
	 *            the {@link Map} {@link Entry} to extract the key of
	 * @return the {@link String} key from the specified {@link Entry} as an {@link Integer}
	 */
	public static Integer getStringKeyAsInteger(Entry<?, ?> untypedMapEntry)
	{
		Object untypedKey = untypedMapEntry.getKey();
		if (untypedKey == null)
			throw new IllegalArgumentException("Entry key missing: " + untypedMapEntry);
		if (!(untypedKey instanceof String))
			throw new IllegalArgumentException("Unexpected type for entry key: " + untypedKey);

		String stringKey = (String) untypedKey;
		if (!PATTERN_INTEGER.matcher(stringKey).matches())
			throw new IllegalArgumentException("Unexpected format for entry key: " + stringKey);

		Integer integerKey = Integer.parseInt(stringKey);
		return integerKey;
	}

	/**
	 * Extracts the value from the specified {@link Entry} as a {@link Map}.
	 * 
	 * @param untypedMapEntry
	 *            the {@link Map} {@link Entry} to extract the value of
	 * @return the value from the specified {@link Entry} as a {@link Map}
	 */
	public static Map<?, ?> getValueAsMap(Entry<?, ?> untypedMapEntry)
	{
		Object untypedValue = untypedMapEntry.getValue();
		if (untypedValue == null)
			throw new IllegalArgumentException("Entry value missing: " + untypedMapEntry);
		if (!(untypedValue instanceof Map<?, ?>))
			throw new IllegalArgumentException("Unexpected type for entry value: " + untypedValue);
		return (Map<?, ?>) untypedValue;
	}

	/**
	 * Casts the value to an untyped {@link Map}.
	 * 
	 * @param untypedObject
	 *            the untyped {@link Object} to cast
	 * @return the value, as an untyped {@link Map}
	 */
	public static Map<?, ?> castToMap(Object untypedObject)
	{
		if (untypedObject == null)
			throw new IllegalArgumentException("Object missing: " + untypedObject);
		if (!(untypedObject instanceof Map<?, ?>))
			throw new IllegalArgumentException("Unexpected type for object: " + untypedObject);
		return (Map<?, ?>) untypedObject;
	}

	/**
	 * Returns an arbitrarily ordered array containing the specified {@link Integer}s.
	 * 
	 * @param integers
	 *            the {@link Integer}s to include in the array
	 * @return an arbitrarily ordered array containing the specified {@link Integer}s
	 */
	public static Integer[] toIntegerArray(Set<Integer> integers)
	{
		Integer[] array = new Integer[integers.size()];

		int i = 0;
		for (Integer integer : integers)
			array[i++] = integer;

		return array;
	}
}
